﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using EnsureThat;
using ExtraConstraints;

namespace Robo
{
	/// <summary>
	/// Represents an event in the runtime.
	/// </summary>
	/// <typeparam name="TDelegate">The delegate type.</typeparam>
	public sealed class RuntimeEvent<[DelegateConstraint]TDelegate> : RuntimeEvent
		where TDelegate : class
	{
		private Action<TDelegate> _add;
		private Action<TDelegate> _remove;
		/// <summary>
		/// Adds a handler to the event.
		/// </summary>
		/// <param name="handler">The handler to add.</param>
		public void Add(TDelegate handler)
		{
			Ensure.That(() => handler, Is.NotNull);
			_add(handler);
		}
		/// <summary>
		/// Removes a handler from the event.
		/// </summary>
		public void Remove(TDelegate handler)
		{
			Ensure.That(() => handler, Is.NotNull);
			_remove(handler);
		}
		/// <summary>
		/// Creates the runtime event from specified add and remove delegates.
		/// </summary>
		public RuntimeEvent(Action<TDelegate> add, Action<TDelegate> remove, string name = "")
		{
			Ensure.That(() => add, Is.NotNull);
			Ensure.That(() => remove, Is.NotNull);
			_remove = remove;
			_add = add;
			Name = name;
		}
	}
	/// <summary>
	/// Extensions relating to runtime events.
	/// </summary>
	public static class EventExtensions
	{
		/// <summary>
		/// Gets the runtime representation of <paramref name="info"/> on object <paramref name="source"/>.
		/// </summary>
		/// <permission cref="ReflectionPermission">This method uses reflection to dynamically generate a <see cref="RuntimeEvent{TDelegate}"/></permission>
		[ReflectionPermission(SecurityAction.Assert, Flags = ReflectionPermissionFlag.RestrictedMemberAccess)]
		public static RuntimeEvent GetRuntime(this EventInfo info, object source)
		{
			Ensure.That(() => info, Is.NotNull);
			var name = info.Name;
			var t = info.EventHandlerType;
			var add = info.GetAddMethod();
			var rem = info.GetRemoveMethod();
			var targetType = typeof(RuntimeEvent<>).MakeGenericType(t);
			var act = typeof(Action<>).MakeGenericType(t);
			var addDel = add.CreateDelegate(act, source);
			var remDel = rem.CreateDelegate(act, source);
			var constructor = targetType.GetConstructor(new[] { act, act, typeof(string) });
			var ret = constructor.Invoke(new object[] { addDel, remDel, name });
			return (RuntimeEvent)ret;
		}
	}
	/// <summary>
	/// Base class for <see cref="RuntimeEvent{T}"/>
	/// </summary>
	public abstract class RuntimeEvent
	{
		/// <summary>
		/// The event name.
		/// </summary>
		public string Name { get; protected set; }
	}
}
